package task

import (
	"codedef"
	"github.com/golang/protobuf/proto"
	"log"
	"ocean"
	"oceanserv"
	"time"
)

const (
	TC_SERR_OFFLINE       = 1
	TC_SERR_BUSY          = 2
	TC_SERR_SEND          = 3
	TC_SERR_REPLY_TIMEOUT = 4
	TC_SERR_NETWORKBREAK  = 5

	CONST_SHORT_TIMEOUT_SECONDS                = 60
	CONST_DURATION_TIMER_WAIT_MINUTES          = 3
	CONST_VALVE_CLOSE_RECOVER_TIME_PAD_MINUTES = 10
	CONST_PUMP_OPEN_RECOVER_TIME_MINUTES       = 5
)

type TController struct {
	gwId     string
	hasReply bool
	id       int32
	serr     int
	cerr     int
}

type TControllerGroup struct {
	controllers []*TController
}

type Task struct {
	account          string
	id               string
	ttype            string
	perbatch         int
	duration         int
	err              int
	isCancel         bool
	cancelAble       bool
	pumpGroup        *TControllerGroup
	valveAlwaysOpen  *TControllerGroup
	valveAlwaysClose *TControllerGroup
	valveGroups      []*TControllerGroup
	chanCancel       chan bool
	echan            chan *oceanserv.ControllerEvent
}

func (t *Task) sendValveCmd2Controller(c *oceanserv.Controller, cmd int, autoRecoverTimer int) bool {
	msg := &ocean.SCControllerCmd{}
	var autoRecoverTimerX = int32(autoRecoverTimer)
	msg.AutoRecoverTime = &autoRecoverTimerX

	buf, err := proto.Marshal(msg)
	if err != nil {
		log.Println("sendValveOpen2Controller failed:", err)
		return false
	}

	return c.Send(int32(cmd), buf)
}

func isAllControllerHasReply(tcontrollers []*TController) bool {
	for _, tc := range tcontrollers {
		if !tc.hasReply {
			return false
		}
	}
	return true
}

func (t *Task) keepPumpIfNeed() {
	if t.pumpGroup != nil && len(t.pumpGroup.controllers) > 0 {
		t.openPumps()

		if t.err != 0 {
			log.Println("keepPumpIfNeed failed, task will break, err:", t.err)
		}
	}
}

func (t *Task) waitDuration(minutes int) {
	log.Printf("wait duration, minutes:%d\n", minutes)

	keeppumpTicker := time.NewTicker(time.Duration(CONST_DURATION_TIMER_WAIT_MINUTES) * time.Minute)

	tchan := time.After(time.Duration(minutes) * time.Minute)
	var exit bool = false
	var timeout bool = false
	for !exit && t.err == 0 {
		select {
		case <-tchan:
			exit = true
			timeout = true
			break
		case <-keeppumpTicker.C:
			t.keepPumpIfNeed()
			break
		case <-t.chanCancel:
			log.Println("got user cancel request")
			if t.cancelAble {
				log.Println("task is cancelable, exit wait duration")
				t.isCancel = true
				exit = true
			}
			break
		}
	}

	keeppumpTicker.Stop()

	// process timeout
	if timeout {
		log.Println("wait duration completed")
	}
}

func (t *Task) getValidControllers(x []*TController, breakOnInvalid bool) []*oceanserv.Controller {
	controllers := make([]*oceanserv.Controller, len(x))

	var index int = 0
	for _, tc := range x {
		gwId := tc.gwId
		ctlId := tc.id
		controller := oceanserv.GetController(gwId, ctlId)

		if controller == nil {
			log.Println("valve controller is offline:", controller.GetId())
			tc.serr = TC_SERR_OFFLINE

			if breakOnInvalid {
				return nil
			} else {
				continue
			}

		}

		controllers[index] = controller
		index++
	}

	return controllers[:index]
}

func (t *Task) logTaskBegin() {
	var pumpCount int = 0
	var valveGroupCount int = 0
	var valveCount int = 0
	var valveAlwaysOpenCount int = 0
	var valveAlwaysCloseCount int = 0

	if t.pumpGroup != nil {
		pumpCount = len(t.pumpGroup.controllers)
	}

	if t.valveGroups != nil {
		valveGroupCount = len(t.valveGroups)
	}

	if valveGroupCount > 0 {
		for _, g := range t.valveGroups {
			valveCount += len(g.controllers)
		}
	}

	if t.valveAlwaysOpen != nil {
		valveAlwaysOpenCount = len(t.valveAlwaysOpen.controllers)
	}

	if t.valveAlwaysClose != nil {
		valveAlwaysCloseCount = len(t.valveAlwaysClose.controllers)
	}

	log.Printf("task %s execute, pump count:%d, valve group:%d, valve count:%d, always-open:%d, always-close:%d\n",
		t.id, pumpCount, valveGroupCount, valveCount, valveAlwaysOpenCount, valveAlwaysCloseCount)
}

func (t *Task) processAlwaysConditions() {

	if t.valveAlwaysOpen != nil {
		t.ensureAlwaysOpen()
		if t.err != 0 {
			return
		}
	} else {
		log.Println("task has no always-open valves")
	}

	if t.valveAlwaysClose != nil {
		t.ensureAlwaysClose()
		if t.err != 0 {
			return
		}
	} else {
		log.Println("task has no always-close valves")
	}

}

func (t *Task) reportTaskResult() {
	log.Println("task end, error:", t.err)
}

func (t *Task) getAllValidControllers(breakOnInvalid bool) []*oceanserv.Controller {
	totalGroup := 3 + len(t.valveGroups)
	tgs := make([]*TControllerGroup, totalGroup)
	tgs[0] = t.pumpGroup
	tgs[1] = t.valveAlwaysOpen
	tgs[2] = t.valveAlwaysClose

	copy(tgs[3:], t.valveGroups)

	var totalValve int = 0
	for _, g := range tgs {
		if g != nil {
			totalValve += len(g.controllers)
		}
	}

	tcs := make([]*TController, totalValve)
	var i int = 0
	for _, g := range tgs {
		if g != nil {
			copy(tcs[i:], g.controllers)
			i += len(g.controllers)
		}
	}

	return t.getValidControllers(tcs, breakOnInvalid)
}

func (t *Task) lockAllControllers() {
	log.Println("lockAllControllers")
	t.echan = make(chan *oceanserv.ControllerEvent, 256)
	cc := t.getAllValidControllers(true)
	if cc == nil {
		return
	}

	var i int = 0
	var failed bool = false
	for j, c := range cc {
		i = j
		if c.IsBusy() {
			failed = true
			t.err = codedef.ERR_TASK_CONTROLLER_BUSY
		} else {
			c.Attach(t.echan)
		}
	}

	if failed {
		// fallback
		for k := 0; k < (i - 1); k++ {
			cc[k].Detach()
		}
	}
}

func (t *Task) unlockAllControllers() {
	log.Println("unlockAllControllers")
	cc := t.getAllValidControllers(false)
	for _, c := range cc {
		c.Detach()
	}

	close(t.echan)
	t.echan = nil
}

func (t *Task) execute() {
	log.Println("Task begin execute, id:", t.id)

	defer t.reportTaskResult()

	t.chanCancel = make(chan bool, 1)

	t.processAlwaysConditions()

	if t.err != 0 {
		log.Println("task break, processAlwaysConditions set task error")
		return
	}

	t.lockAllControllers()
	if t.err != 0 {
		log.Println("task break, lockAllControllers set task error")
		return
	}

	defer t.unlockAllControllers()

	valveGroupCount := len(t.valveGroups)
	pumpCount := 0
	if t.pumpGroup != nil {
		pumpCount = len(t.pumpGroup.controllers)
	}

	for i := 0; i < valveGroupCount; i++ {
		log.Println("task try work on valve group:", i)
		t.workOnValves(i)

		if pumpCount > 0 {
			t.openPumps()

			if t.err != 0 {
				log.Println("task try open pump failed:", t.err)
				break
			}
		} else {
			// just for test purpose, in production enviromnent must return err
			log.Println("task has no pump, only open valves")
			// break
		}

		if t.err != 0 {
			log.Println("task has error, stop at group:", i)
			break
		}

		t.cancelAble = true
		// wait controller duration
		t.waitDuration(t.duration)
		t.cancelAble = false

		if t.isCancel {
			log.Println("task be cancel, stop at group:", i)
			break
		}
	}

	if pumpCount > 0 {
		log.Println("task try close pump")
		t.closePumps()
	}

	t.resetAllValves()
}

func (t *Task) openPumps() {
	log.Println("openPumps, controller count: ", len(t.pumpGroup.controllers))
	t.command2Valves(t.pumpGroup.controllers, codedef.CmdControllerOpen, CONST_PUMP_OPEN_RECOVER_TIME_MINUTES)
}

func (t *Task) closePumps() {
	log.Println("closePumps, controller count: ", len(t.pumpGroup.controllers))
	t.command2Valves(t.pumpGroup.controllers, codedef.CmdControllerClose, 0)
}

func (t *Task) ensureAlwaysClose() {
	log.Println("ensureAlwaysClose, controller count: ", len(t.valveAlwaysClose.controllers))
	t.command2Valves(t.valveAlwaysClose.controllers, codedef.CmdControllerClose, 0)
}

func (t *Task) ensureAlwaysOpen() {
	log.Println("ensureAlwaysOpen, controller count: ", len(t.valveAlwaysOpen.controllers))
	t.command2Valves(t.valveAlwaysOpen.controllers, codedef.CmdControllerOpen, 0)
}

func (t *Task) processControllerEvent(ce *oceanserv.ControllerEvent, tcontrollers []*TController) {
	controllerId := ce.Tunnel.GetTarget()
	var tc *TController = nil
	for _, x := range tcontrollers {
		if x.id == controllerId {
			tc = x
			break
		}
	}

	if tc == nil {
		log.Println("processControllerEvent failed, no controller:", controllerId)
		return
	}

	tc.hasReply = true
	switch ce.Event {
	case oceanserv.CEVENT_GW_BREAK:
		tc.serr = TC_SERR_NETWORKBREAK
		t.err = codedef.ERR_TASK_CONTROLLER_NOT_VALID
		log.Println("processControllerEvent, set task err cause CEVENT_GW_BREAK")
		break
	case oceanserv.CEVENT_LEAVE:
		tc.serr = TC_SERR_OFFLINE
		t.err = codedef.ERR_TASK_CONTROLLER_NOT_VALID
		log.Println("processControllerEvent, set task err cause CEVENT_LEAVE")
		break
	case oceanserv.CEVENT_MSG:
		reply := &ocean.CSControllerCmdReply{}
		err := proto.Unmarshal(ce.Tunnel.GetData(), reply)
		if err != nil {
			log.Println("processControllerEvent failed:", err)
		} else {
			tc.cerr = int(reply.GetResult())
		}
		break
	}
}

func (t *Task) waitControllersReply(tcontrollers []*TController) {
	for _, tc := range tcontrollers {
		tc.hasReply = false
	}

	log.Printf("wait controllers reply, count:%d, seconds:%d\n", len(tcontrollers), CONST_SHORT_TIMEOUT_SECONDS)

	tchan := time.After(time.Duration(CONST_SHORT_TIMEOUT_SECONDS) * time.Second)
	var exit bool = false
	var timeout bool = false
	for !exit {
		select {
		case ce := <-t.echan:
			log.Printf("got controller event:%d, id:%d\n", ce.Event, int(ce.Id))
			t.processControllerEvent(ce, tcontrollers)
			if isAllControllerHasReply(tcontrollers) || t.err != 0 {
				exit = true
			}
			break
		case <-tchan:
			exit = true
			timeout = true
			break
		}
	}

	// process timeout
	if timeout {
		log.Println("wait controller reply got timeout")
		for _, tc := range tcontrollers {
			if !tc.hasReply {
				log.Println("controller timeout, id:", tc.id)
				tc.serr = TC_SERR_REPLY_TIMEOUT
			}
		}

		t.err = codedef.ERR_TASK_TIMEOUT
	}
}

func (t *Task) command2Valves(x []*TController, cmd int, recovertime int) {
	log.Printf("now send command to controllers, count:%d, cmd:%d, recovertime:%d\n",
		len(x), cmd, recovertime)

	controllers := t.getValidControllers(x, true)
	if controllers == nil || t.err != 0 {
		return
	}

	for _, oc := range controllers {
		if !t.sendValveCmd2Controller(oc, cmd, recovertime) {
			t.err = codedef.ERR_TASK_SEND2_CONTROLLER
			return
		}
	}

	log.Println("now wait controllers reply, controller count:", len(x))
	// wait controller reply
	t.waitControllersReply(x)
}

func (t *Task) resetAllValves() {
	log.Println("resetAllValves")
	for _, g := range t.valveGroups {
		t.command2Valves(g.controllers, codedef.CmdControllerOpen, 0)
	}
}

func (t *Task) workOnValves(i int) {
	log.Println("workOnValves, group:", i)
	o := t.valveGroups[i]
	t.command2Valves(o.controllers, codedef.CmdControllerOpen, 0)
	if t.err != 0 {
		return
	}

	for j, g := range t.valveGroups {
		if j != i {
			t.command2Valves(g.controllers, codedef.CmdControllerClose, t.duration+CONST_VALVE_CLOSE_RECOVER_TIME_PAD_MINUTES)
			if t.err != 0 {
				return
			}
		}
	}
}
